/*
 * Copyright Aristool AG Switzerland
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.opprua.tidy.util;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * TODO: add Java doc
 *
 * Prepared for first commit: 15.04.2007
 *
 * @author Lin Li
 */
public class XBuilder {

  private String tag = "";
  protected List<Object> elements = new ArrayList<Object>();
  private Map<String, String> attributes = new TreeMap<String, String>();
  protected boolean tight = false;

  private static class CData extends XBuilder {

    CData() {
      super("");
    }

    public void dump(CodePrint p) {
      p.print("<![CDATA[");
      if (!tight) {
        p.println();
        p.einruecken();
      }
      // this element has children, write them.
      for (int i = 0; i < this.elements.size(); ++i) {
        Object e = this.elements.get(i);
        if (i > 0) {
          p.println();
        }
        if (e instanceof XBuilder) {
          ((XBuilder) e).dump(p);
        } else {
          p.print(StringUtil.replace(String.valueOf(e), "]]>", "]]&gt;"));
        }
      }
      if (!tight) {
        p.println();
        p.ausruecken();
      }
      p.println("]]>");
    }
  }

  /**
   * <p>
   * Tight print for this element. A tightly printed element removes all blanks
   * before and after its inside string. This is useful for HTML generation
   * where the empty spaces do matter. This only works when the element has only
   * text inside it.
   * </p>
   */
  public XBuilder setTight() {
    tight = true;
    return this;
  }

  /**
   * <p>
   * Create a new element with the given tag name.
   * </p>
   * 
   * @param tagName
   *          the tag name of the new element.
   */
  public XBuilder(String tagName) {
    tag = tagName;
  }

  /**
   * <p>
   * Add a new subelement.
   * </p>
   * 
   * @param x
   *          the new subelement.
   * @return this, for chained calls.
   */
  public XBuilder addElement(XBuilder x) {
    if (x != null) {
      elements.add(x);
    }
    return this;
  }

  /**
   * <p>
   * Add a sub element that is already formated into a string.
   * </p>
   * 
   * @param s
   *          string presentation of the new element.
   * @return this, for chained calls.
   */
  public XBuilder addElement(String s) {
    if (s != null && s.trim().length() > 0) {
      elements.add(s.trim());
    }
    return this;
  }

  /**
   * <p>
   * Add text element to this element.
   * </p>
   * 
   * @param s
   *          the text element
   * @return this, for chained calls
   */
  public XBuilder addText(String s) {
    if (s == null || s.trim().length() == 0) {
      return this;
    }
    StringBuffer res = new StringBuffer();
    for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);
      if (c < 0x20 && c != 0x9 && c != 0xa && c != 0xd) {
        res.append(' ');
      } else {
        res.append(c);
      }
    }
    elements.add(res.toString());
    return this;
  }

  /**
   * <p>
   * Creaate a new subelement.
   * </p>
   * 
   * @param s
   *          the tag name of the new element.
   * @return the newly created subelement.
   */
  public XBuilder newElement(String s) {
    XBuilder x = new XBuilder(s);
    addElement(x);
    return x;
  }

  /**
   * <p>
   * Add a new CData element to this element. The CData can contain its own
   * elements and subelements, although those will be "quoted out" by the XML
   * semantics.
   * </p>
   * 
   * @return the CData element.
   */
  public XBuilder addCData() {
    CData x = new CData();
    addElement(x);
    return x;
  }

  /**
   * <p>
   * Add a new attribute to this element.
   * </p>
   * 
   * @param name
   *          name of the attribute
   * @param value
   *          value of the attribute (without quotes).
   * @return this, for chained calls
   */
  public XBuilder addAttribute(String name, String value) {
    attributes.put(name, value);
    return this;
  }

  /**
   * <p>
   * Dump this element into a code writer
   * </p>
   * 
   * @param p
   *          the code writer
   */
  public void dump(CodePrint p) {
    // start
    p.print("<" + tag);

    // write name as the first attribute, if exists
    String v = (String) attributes.get("name");
    if (v != null && v.trim().length() > 0) {
      p.print(" name=\"" + v.trim() + "\"");
    }

    // write the tag with its attributes
    Iterator<String> iter = attributes.keySet().iterator();
    while (iter.hasNext()) {
      String key = iter.next();
      if (key.equals("name")) {
        continue;
      }
      String value = (String) attributes.get(key);
      if (value != null && value.trim().length() > 0) {
        p.print(" " + key + "=\"" + value.trim() + "\"");
      }
    }

    // no sub elements, close it and end of the story
    if (elements.size() == 0) {
      p.println("/>");
      return;
    }

    if (elements.size() == 1 && (elements.get(0) instanceof String)) {
      String str = ((String) elements.get(0)).trim();
      if (str.length() < 20 && str.indexOf(" ") < 0 || tight) {
        p.println(">" + str + "</" + tag + ">");
        return;
      }
    }

    p.println(">");

    p.einruecken();
    // this element has children, write them.
    for (int i = 0; i < elements.size(); ++i) {
      Object e = elements.get(i);
      if (XBuilder.class.isAssignableFrom(e.getClass())) {
        ((XBuilder) e).dump(p);
      } else {
        p.println(String.valueOf(e));
      }
    }
    p.ausruecken();
    p.println("</" + tag + ">");
  }

  /**
   * <p>
   * Dump this element into a file.
   * </p>
   * 
   * @param fileName
   *          name of the file
   */
  public void dump(String fileName) {
    CodePrint c = CodePrint.create(fileName);
    dump(c);
    c.close();
  }

  /**
   * <p>
   * Create a string presentation of the element.
   * </p>
   * 
   * @return the string presentation of the element.
   */
  public String dump() {
    StringWriter s = new StringWriter();
    CodePrint c = CodePrint.create(new PrintWriter(s));
    dump(c);
    c.close();
    return s.getBuffer().toString();
  }

}